
package uk.co.revisual.puremvc.loader.model
{
	/**
	Copyright (c) 2008 neil manuell (revisual)

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:
	
	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.
	
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
	
	
	* ...
	* @author neil manuell 
	*/
	
	import flash.events.*;
	import flash.utils.Dictionary;
	
	import uk.co.revisual.puremvc.loader.clients.ClientBase;
	import uk.co.revisual.puremvc.loader.etypes.LoaderNotificationNames;
	import uk.co.revisual.puremvc.loader.events.ClientEvent;
	import uk.co.revisual.puremvc.loader.events.LoaderErrorEvent;
	import uk.co.revisual.puremvc.loader.events.LoaderEvent;
	import uk.co.revisual.puremvc.loader.interfaces.*;


	

	
	public class Loader extends EventDispatcher
	{
		private var mDataMap:Dictionary;
		private var mClientMap:Object;
		private var mLoadQue:Array;
		private var mIsLoading:Boolean;
		private var mIsAbortLoading:Boolean;
		private var mIsAutoLoad:Boolean;
		private var mIsChanged:Boolean;
		private var mCount:int;
		private var mNumInQue:int;
		
		private var mCurrentItemBytesLoaded:Number;
        private var mCurrentItemBytesTotal:Number;

        private var mQueLoadProgress:Number;

		private var mQueSort:Function;
		
		
		/**
		 * the constructor
		 */
		public function Loader() 
		{
			init();
		}
		
		/**
		 * indicates whether the load que is active
		 */
		public function get isActive():Boolean{
			return mIsLoading;
		}
		
		/**
		 * the number of items left in the load que
		 */
		public function get queLength():int{
			return mLoadQue.length;
		}
		

		
		/**
		 * the function that defines the load que order
		 */
		public function set queSortFunction(param:Function):void
		{
			mQueSort = param;
			mIsChanged = true;
		}
		
		/**
		 * defines whether the load que should automatically start when an item has been added
		 */
		public function get autoLoad():Boolean
		{
			return mIsAutoLoad;
		}
		
		/**
		 * @private
		 */
		public function set autoLoad(param:Boolean):void
		{
			mIsAutoLoad = param;
		}
		
		/**
		 * the load que
		 */
		private function get loadQue():Array{
			return mLoadQue;
		}
		
		public function getAsset(url:String):IAssetInfo
		{
			if (mDataMap[url] == null)
			{
				return new LoadRequest(url);
			}else
			{
				return mDataMap[url]
			}
		}
		
		public function hasLoaded(url:String):Boolean
		{
			if (mDataMap[url] == null)
			{
				return false;
			}else
			{
				return (mDataMap[url] as IAssetInfo).hasLoaded;
			}
		}
		
		public function deleteAsset(url:String):void
		{
			if (mDataMap[url])
			{
				LoadRequest(mDataMap[url]).deconstruct();
				delete mDataMap[url];
			}
		}
		
		public function addToQue(param:IAssetInfo):void{
			
			var request:LoadRequest;
			
			request = LoadRequest(param)
			
			if(request.clientType == null) throw new Error("the clientType must be set")
			
			loadQue.push(request);
			mNumInQue++;
			
			mIsChanged = true;
			
			if(!mIsLoading && mIsAutoLoad)
			{
				startQue();		
			}
		}
		
		public function stopQue():void{
			
			mIsAbortLoading = true;
			
		}
		public function startQue():void{
			
			mIsLoading = true;
			loadNext();
		}
		
		//TODO: impliment this
		public function deconstruct():void
		{
		
		}
		
		public function registerClient(id:String, c:Class):void
		{
			mClientMap[id] = c;
		}
		
		public function removeClient(id:String):void
		{
			if(mClientMap[id])
			{
				delete mClientMap[id];
			}
		}
		
		public function getClient(id:String):Class{
			
			if (mClientMap[id] == null)
			{
				throw new Error("not client registered with id: "+id)
			}
			
			return Class(mClientMap[id])
		}
		
		private function init():void
		{
			mDataMap = new Dictionary();
			mClientMap = new Object();
			mLoadQue = new Array();
			mIsAutoLoad = true;
		}
		
		
		
		private function loadNext():void
		{			
			var request:LoadRequest,
				clazz:Class,
				client:ClientBase,
				e:LoaderEvent;
			
			if(mIsChanged)
			{
				sortQue();
			}
			
			request = LoadRequest (loadQue.shift() );
								
			addListeners(request);
			
			clazz = getClient(request.clientType);
			client = new clazz(request);
			client.load();
			
			e = new LoaderEvent(	LoaderNotificationNames.ITEM_STARTED,
										mCurrentItemBytesLoaded,
										mCurrentItemBytesTotal,
										request.url,
										mNumInQue,
										mNumInQue - mCount,
										mQueLoadProgress			);
			dispatchEvent(e)
			
		}
		

		
		
		
		private function sortQue():void
		{
			if (mQueSort != null)
			{
				loadQue.sort(mQueSort)
			}
			
			mIsChanged = false;
		}
		
		
		private function ioErrorHandler(event:IOErrorEvent):void
		{
			var request:LoadRequest,
				e:LoaderErrorEvent;	
			
			request = event.target as LoadRequest;
			removeListeners(request);


			
			mNumInQue--;
			mCount--;
			
			e = new LoaderErrorEvent(	LoaderErrorEvent.ITEM_IOERROR, 
											event.text, 
											request.url	);

			dispatchEvent(e);
			
			
			endLoadCycle(request);
			
		}
		
		private function loadRequestCompleteHandler(event:Event):void
		{
			var request:LoadRequest,
				e:LoaderEvent;	
			
			mCount++;		
			request = event.target as LoadRequest;
			removeListeners(request);
			addRequest(request);
			
			
			
			e = new LoaderEvent(	LoaderNotificationNames.ITEM_COMPLETED,
									mCurrentItemBytesLoaded,
									mCurrentItemBytesTotal,
									request.url,
									mNumInQue,
									mNumInQue - mCount,
									mQueLoadProgress			);
										
			dispatchEvent(e);
			
			endLoadCycle(request);
			
			
			
		}
		
		private function priorityChangeHandler(event:Event):void
		{
			mIsChanged = true;
			
		}
		
		
		
		private function removeListeners(request:LoadRequest):void
		{
			
			request.removeEventListener(ClientEvent.COMPLETE, loadRequestCompleteHandler);
			request.removeEventListener(Event.CHANGE, priorityChangeHandler);
			request.removeEventListener(ProgressEvent.PROGRESS, loadRequestProgressHandler);
			request.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
					
			
		}
		
		private function addListeners(request:LoadRequest):void
		{
			request.addEventListener(ClientEvent.COMPLETE, loadRequestCompleteHandler);
			request.addEventListener(Event.CHANGE, priorityChangeHandler);
			request.addEventListener(ProgressEvent.PROGRESS, loadRequestProgressHandler);
			request.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
			
					
			
		}
		
		protected function addRequest(request:LoadRequest):void
		{
			if (request.store)
			{
				mDataMap[request.url] = request;
			}
			
		}
		
		private function endLoadCycle(request:LoadRequest):void
		{
			
			var e:LoaderEvent
			
			
			
			if(loadQue.length > 0 || mIsAbortLoading)
			{
				loadNext();	
				
			}else
			{
				
				mIsLoading = false;
				mIsAbortLoading = false;
				

				
				e = new LoaderEvent(	LoaderNotificationNames.QUE_COMPLETED,
											mCurrentItemBytesLoaded,
											mCurrentItemBytesTotal,
											request.url,
											mNumInQue,
											mNumInQue - mCount,
											mQueLoadProgress			);
											
				dispatchEvent(e)
				
				resetVars()
				
			}
		}
		
		private function resetVars():void
		{
			mCount = 0;
			mNumInQue = 0;
			mCurrentItemBytesLoaded = 0;
        	mCurrentItemBytesTotal = 0;


        	mQueLoadProgress = 0;
		}
		
		private function loadRequestProgressHandler(event:ProgressEvent):void
		{
			var request:LoadRequest,
				e:LoaderEvent;
				
			request = event.target as LoadRequest;
			
			
			// this nice equation from QueueLoader @ hydrotik - thanks
			mQueLoadProgress = (((mCount * (100 / (mNumInQue))) + ((event.bytesLoaded / event.bytesTotal) * (100 / (mNumInQue)))) * .01);
			
			
			mCurrentItemBytesLoaded = event.bytesLoaded;
			mCurrentItemBytesTotal = event.bytesTotal;
			
			
			e = new LoaderEvent(	LoaderNotificationNames.QUE_PROGRESS,
										mCurrentItemBytesLoaded,
										mCurrentItemBytesTotal,
										request.url,
										mNumInQue,
										mNumInQue - mCount,
										mQueLoadProgress			);
											
			dispatchEvent(e)
			
		}
		
		
		

	}
	
}



	import flash.events.EventDispatcher;
	import flash.net.URLRequest;
	import flash.system.LoaderContext;
	import flash.media.SoundLoaderContext;
	import flash.events.Event;
	import mx.core.IFlexModuleFactory;
	import mx.modules.IModuleInfo;

	import uk.co.revisual.puremvc.loader.interfaces.*;

	import uk.co.revisual.puremvc.loader.interfaces.IAssetInfo;
	import uk.co.revisual.puremvc.loader.interfaces.IClientInfo;
	import uk.co.revisual.puremvc.loader.interfaces.IDeconstruct;
	import uk.co.revisual.puremvc.loader.interfaces.ICreate;
	
	/**
	* ...
	* @author DefaultUser (Tools -> Custom Arguments...)
	*/
	class LoadRequest extends EventDispatcher implements IAssetInfo, IClientInfo
	{
		private var mHasLoaded:Boolean;
		private var mAsset:*;
		private var mFileType:String;
		private var mURLReq:URLRequest;
		private var mAssetType:String;
		private var mLoaderContext:Object;
		private var mStore:Boolean;
		private var mPriority:int;
		private var mSetLoaderType:String;
		private var mDeconstructor:IDeconstruct;
		
		
		public function LoadRequest(url:String):void
		{
			this.url = url;
			this.store = true;
		}
		
		public function get hasLoaded():Boolean
		{
			return mHasLoaded;
		}
		
		public function get fileType():String
		{
			return mFileType;
		}
		
		public function get url():String
		{
			return mURLReq.url;
		}
		
		public function set url(param:String):void
		{
			mFileType = getExtention(param);
			mURLReq = new URLRequest(param);
		}
		
		public function get priority():int
		{
			return mPriority;
		}
		
		public function set priority(param:int):void
		{
			mPriority = param;
			dispatchEvent( new Event(Event.CHANGE));
		}
		
		public function get store():Boolean
		{
			return mStore;
		}
		
		public function set store(param:Boolean):void
		{
			mStore = param;
		}
		
		public function set context(param:Object):void
		{
			mLoaderContext  = param;
		}
		
				
		public function set deconstructor(param:IDeconstruct):void
		{
			mDeconstructor  = param;
		}
		
		public function get clientType():String
		{
			return mSetLoaderType;
		}
		
		public function set clientType(param:String):void
		{
			mSetLoaderType  = param;
		}
		
		public function getAsset():*
		{
			
			switch(true)
			{
				case(!hasLoaded):
					throw new Error("asset has not loaded yet");
					
				case(mAsset is ICreate):
					return ICreate(mAsset).create("asset")
					
				default:
					return mAsset
				
				
			}
			if(mAsset is ICreate)
			{
		
				return ICreate(mAsset).create("asset")
			}
			else
			{
				return mAsset;
			}
			
		}
		
		public function setAsset(asset:*):void
		{
			
			mHasLoaded = true;
			mAsset = asset;
		}
				
		public function get urlRequest():URLRequest
		{
			return mURLReq;
		}
		
		public function get loaderContext():LoaderContext
		{
			return mLoaderContext as LoaderContext
		}
		
		public function get soundLoaderContext():SoundLoaderContext
		{
			return mLoaderContext as SoundLoaderContext
		}
		
		public function relayEvent(event:Event):void
		{
			dispatchEvent(event);
		}
		
		public function create(id:String = ""):*
		{
			var asset:*
			
			switch(true)
			{
									
				case(mAsset is ICreate):
					return ICreate(mAsset).create(id);
					

				default:
					throw new Error("not an ICreate");
					
			}
		
				
		}
		
		public function deconstruct():void
		{
			if(mDeconstructor)
			{
				mDeconstructor.deconstruct();
			}
			
			mAsset = null;
			
		}
		
		private function getExtention(url:String):String
		{
			var urlSplit:Array;
			
			urlSplit = url.split(".");
			
			return urlSplit[urlSplit.length-1]
		}
		
		
		

	}
	
	
	
	
	